/*
 * Copyright 2010 WANdisco, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */
#include <stdlib.h>
#include <string.h>
#include <apr_pools.h>

#include "svn_pools.h"
#include "svn_error.h"
#include "svn_fs.h"
#include "svn_repos.h"
#include "svn_path.h"
#include "svn_delta.h"
#include "svn_config.h"
#include "svn_props.h"

typedef struct edit_baton
{
  svn_fs_t *fs;
  svn_fs_root_t *rev_root;
  const char *root_path;
  apr_pool_t *pool;
} edit_baton;

typedef struct dir_baton
{
  struct edit_baton *edit_baton;
  const char *full_path;
} dir_baton;


typedef struct file_baton
{
  struct edit_baton *edit_baton;
  const char *path;
} file_baton;

static svn_error_t *
test_target_revision(void *edit_baton, svn_revnum_t rev, apr_pool_t *pool) {
  printf("[test_target_rev] %ld\n", rev);
  return SVN_NO_ERROR;
}

static svn_error_t *
test_open_root(void *edit_baton,
               svn_revnum_t base_revision,
               apr_pool_t *dir_pool,
               void **root_baton)
{
  struct dir_baton *db = apr_pcalloc(dir_pool, sizeof(*db));
  struct edit_baton *eb = edit_baton;

  db->full_path = eb->root_path;
  db->edit_baton = edit_baton;

  *root_baton = db;
  printf("[test_open_root] \"%s\"\n", eb->root_path);
  
  return SVN_NO_ERROR;
}

static svn_error_t *
test_delete_entry(const char *path,
                  svn_revnum_t revision,
                  void *parent_baton,
                  apr_pool_t *pool)
{
  struct dir_baton *pb = parent_baton;

  printf("[test_delete_entry] %s %s\n", pb->full_path, path);
  /* Construct the full path of this entry and delete it from the txn. */
  return SVN_NO_ERROR;
}

static svn_error_t *
test_add_directory(const char *path,
                   void *parent_baton,
                   const char *copyfrom_path,
                   svn_revnum_t copyfrom_revision,
                   apr_pool_t *dir_pool,
                   void **child_baton)
{
  struct dir_baton *pb = parent_baton;
  struct edit_baton *eb = pb->edit_baton;
  struct dir_baton *db = apr_pcalloc(dir_pool, sizeof(*db));

  /* Construct the full path of the new directory */
  db->full_path = svn_path_join(eb->root_path, path, eb->pool);
  printf("[test_add_dir] %s %s\n", eb->root_path, path);
  db->edit_baton = eb;

  if (copyfrom_path)  /* add with history */
    {
      svn_fs_root_t *rev_root = NULL;
    }
  else  /* add without history */
    ;

  *child_baton = db;
  return SVN_NO_ERROR;
}

static svn_error_t *
test_open_directory(const char *path,
                    void *parent_baton,
                    svn_revnum_t base_revision,
                    apr_pool_t *dir_pool,
                    void **child_baton)
{
  struct dir_baton *pb = parent_baton;
  struct edit_baton *eb = pb->edit_baton;
  struct dir_baton *db = apr_pcalloc(dir_pool, sizeof(*db));
  svn_fs_root_t *rev_root = NULL;

  /* Construct the full path of the new directory */
  db->full_path = svn_path_join(eb->root_path, path, eb->pool);
  printf("[test_open_dir] %s\n", db->full_path);
  db->edit_baton = eb;

  *child_baton = db;
  return SVN_NO_ERROR;
}

static svn_error_t *
test_change_dir_prop(void *parent_baton,
                     const char *name, const svn_string_t *value,
                     apr_pool_t *pool)
{
  struct dir_baton *db = parent_baton;
  struct edit_baton *eb = db->edit_baton;
  fprintf(stdout, "[test_change_dir_prop] %s: '%s'='%s'\n",
          db->full_path, name, value->data);

  /* Construct the full path of this entry and change the property. */
  return SVN_NO_ERROR;
}

static svn_error_t *
test_close_dir(void *dir_baton, apr_pool_t *pool) {
  printf("[test_close_dir]\n");
  return SVN_NO_ERROR;
}

static svn_error_t *
test_absent_dir(const char *path, void *parent_baton, apr_pool_t *pool) {
  printf("[test_absent_dir] %s\n", path);
  return SVN_NO_ERROR;
}

static svn_error_t *
test_add_file(const char *path,
              void *parent_baton,
              const char *copyfrom_path,
              svn_revnum_t copyfrom_revision,
              apr_pool_t *file_pool,
              void **file_baton)
{
  struct dir_baton *db = parent_baton;
  struct edit_baton *eb = db->edit_baton;
  struct file_baton *fb = apr_pcalloc(file_pool, sizeof(*fb));

  /* Fill in the file baton. */
  fb->path = svn_path_join(eb->root_path, path, eb->pool);
  printf("[test_add_file] %s %s @ %ld\n", eb->root_path, path, copyfrom_revision);
  fb->edit_baton = eb;

  if (copyfrom_path)  /* add with history */
    {
      svn_fs_root_t *rev_root = NULL;
    }
  else  /* add without history */
    ;

  *file_baton = fb;
  return SVN_NO_ERROR;
}

static svn_error_t *
test_open_file(const char *path,
               void *parent_baton,
               svn_revnum_t base_revision,
               apr_pool_t *file_pool,
               void **file_baton)
{
  struct dir_baton *pb = parent_baton;
  struct edit_baton *eb = pb->edit_baton;
  struct file_baton *fb = apr_pcalloc(file_pool, sizeof(*fb));
  svn_fs_root_t *rev_root = NULL;

  /* Fill in the file baton. */
  fb->path = svn_path_join(eb->root_path, path, eb->pool);
  fb->edit_baton = eb;
  printf("[test_open_file] %s %s\n", eb->root_path, path);

  *file_baton = fb;
  return SVN_NO_ERROR;
}

static svn_error_t *
noop_window_handler(svn_txdelta_window_t *window,
                    void *baton)
{
  if (window != NULL) {
    const svn_string_t *str = window->new_data;
    if (str != NULL && str->data != NULL) {
      fprintf(stdout, "[window###] ");
      fflush(stdout);
      write(1, str->data, str->len);
      printf("###\n");
    } else {
      printf("[window] NO DATA\n");
    }
  } else {
    printf("[window] NO WINDOW\n");
  }
  return SVN_NO_ERROR;
}

static svn_error_t *
test_apply_textdelta(void *file_baton,
                     const char *base_checksum,
                     apr_pool_t *pool,
                     svn_txdelta_window_handler_t *handler,
                     void **handler_baton)
{
  struct file_baton *fb = file_baton;
  printf("[test_apply_textdelta] %s\n", fb->path);
  *handler = noop_window_handler;
  *handler_baton = NULL;
  return SVN_NO_ERROR;
}

static svn_error_t *
test_change_file_prop(void *file_baton,
                      const char *name, const svn_string_t *value,
                      apr_pool_t *pool)
{
  struct file_baton *fb = file_baton;
  printf("[test_change_file_prop] %s: '%s'='%s'\n", fb->path, name, value->data);

  return SVN_NO_ERROR;
}

static svn_error_t *
test_close_file(void *file_baton,
                const char *text_checksum, apr_pool_t *pool) {
  struct file_baton *fb = file_baton;
  printf("[test_close_file]\n");
  return SVN_NO_ERROR;
}


static svn_error_t *
test_absent_file(const char *path, void *parent_baton,
                 apr_pool_t *pool) {
  printf("[test_absent_file]\n");
  return SVN_NO_ERROR;
}

static svn_error_t *
test_close_edit(void *edit_baton, apr_pool_t *pool) {
  printf("test_close_edit\n");
  return SVN_NO_ERROR;
}

static svn_error_t *
test_abort_edit(void *edit_baton, apr_pool_t *pool) {
  printf("[test_abort_edit]\n");
  return SVN_NO_ERROR;
}


/*---------------------------------------------------------------*/

static svn_error_t *
init_editor(const svn_delta_editor_t **editor,
            void **edit_baton,
            svn_fs_t *fs,
            svn_fs_root_t *rev_root,
            const char *path,
            apr_pool_t *pool)
{
  svn_delta_editor_t *my_editor;
  struct edit_baton *my_edit_baton;

  /* Set up the editor. */
  /*my_editor = svn_delta_default_editor(pool); */
  my_editor = apr_palloc(pool, sizeof(svn_delta_editor_t));
  memset(my_editor, 0, sizeof(svn_delta_editor_t));
  
  my_editor->set_target_revision = test_target_revision;
  my_editor->open_root           = test_open_root;
  my_editor->delete_entry        = test_delete_entry;
  my_editor->add_directory       = test_add_directory;
  my_editor->open_directory      = test_open_directory;
  my_editor->change_dir_prop     = test_change_dir_prop;
  my_editor->close_directory     = test_close_dir;
  my_editor->absent_directory    = test_absent_dir;
  my_editor->add_file            = test_add_file;
  my_editor->open_file           = test_open_file;
  my_editor->apply_textdelta     = test_apply_textdelta;
  my_editor->change_file_prop    = test_change_file_prop;
  my_editor->close_file          = test_close_file;
  my_editor->absent_file         = test_absent_file;
  my_editor->close_edit          = test_close_edit;
  my_editor->abort_edit          = test_abort_edit;

  /* Set up the edit baton. */
  my_edit_baton = apr_pcalloc(pool, sizeof(*my_edit_baton));
  my_edit_baton->root_path = apr_pstrdup(pool, path);
  my_edit_baton->pool = pool;
  my_edit_baton->fs = fs;
  my_edit_baton->rev_root = rev_root;
  fprintf(stdout, "[get_editor] \"%s\"\n", my_edit_baton->root_path);
  *editor = my_editor;
  *edit_baton = my_edit_baton;

  return SVN_NO_ERROR;
}

static svn_error_t *
log_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool) {
  fprintf(stderr, "[editor-test.c] log msg for rev %ld\n", log_entry->revision);
  return SVN_NO_ERROR;
}

static svn_error_t *
log_test(svn_repos_t *repo, apr_pool_t *pool) {
  svn_revnum_t start = 0, end = SVN_INVALID_REVNUM;
  int limit = 100;
  fprintf(stderr, "running log test\n");
  SVN_ERR(
svn_repos_get_logs4(repo,
                    NULL,
                    start,
                    end,
                    limit,
                    TRUE,//discover_changed_paths,
                    TRUE,//svn_boolean_t strict_node_history,
                    FALSE,//svn_boolean_t include_merged_revisions,
                    NULL,//const apr_array_header_t *revprops,
                    NULL,//svn_repos_authz_func_t authz_read_func,
                    NULL,//void *authz_read_baton,
                    log_receiver,//svn_log_entry_receiver_t receiver,
                    repo,//void *receiver_baton,
                    pool)
    );
  fprintf(stderr, "ran log test\n");
  exit(0);
  return SVN_NO_ERROR;
    
}
/**
 * repo_path - path to repository on filesystem
 * subpath - path below repos. root to report on
 */
svn_error_t *
run_test(const char *repo_path, const char *fs_base,
         const char *subpath, svn_revnum_t start_rev,
         svn_revnum_t end_rev, apr_pool_t *pool) {
  /* initialize stuff */
  svn_repos_t *repo;
  svn_fs_t *fs;
  /* transaction and tx root */
  svn_fs_root_t *rev_root;

  /* report stuff */
  void *report_baton;
  
  const char *tgt_path = NULL;
  svn_depth_t depth = svn_depth_infinity;
  const edit_baton *my_baton;
  const svn_delta_editor_t *editor;

  /* initialize */
  printf("opening repo \"%s\"\n", repo_path);
  SVN_ERR(svn_repos_open(&repo, repo_path, pool));
  printf("opened repo...\n");
  /*SVN_ERR(log_test(repo, pool));*/
  fs = svn_repos_fs(repo);
  printf("opened repo_fs...\n");
  SVN_ERR(svn_fs_revision_root(&rev_root, fs, start_rev, pool));
  /* editor */
  SVN_ERR(init_editor(&editor, &my_baton, fs, rev_root, fs_base, pool));
  /* report */
  SVN_ERR(
    svn_repos_begin_report2(
      &report_baton,
      end_rev,
      repo,
      fs_base, subpath, tgt_path,
      TRUE, /* text_deltas */
      depth,
      TRUE, /* ignore_ancestry */
      TRUE, /* send_copyfrom_args */
      editor,
      my_baton,
      NULL, /* authz_read_func */
      NULL, /* authz_read_baton */
      pool)
    );

  printf("began report\n");
  // report on everything below what was passed in fs_base/target
  SVN_ERR(svn_repos_set_path3(report_baton, "", start_rev, depth,
                              FALSE, NULL, pool));
  printf("set path\n");
  SVN_ERR(svn_repos_finish_report(report_baton, pool));
  printf("ran on '%s' '%s' '%s'\n", fs_base, subpath, tgt_path);
  return SVN_NO_ERROR;
}
int
main(int argc, char **argv) {
  apr_pool_t *pool;
  const svn_version_t *version;
  svn_error_t *err;
  const char *fs_base = "/", *target = "A/D/H";
  svn_revnum_t start = 0, end = 1;
  printf("editor-test.c running, create a pool RA_DAV_RQ_FAIL=%d\n",
    SVN_ERR_RA_DAV_REQUEST_FAILED);
  apr_initialize();
  /*apr_pool_create(&pool, NULL); */
  pool = svn_pool_create(NULL);
  printf("created a pool\n");

  {
    svn_string_t *str = svn_string_create("hello world", pool);
    printf("created svn_string_t '%s'\n", str->data);
    exit(0);
  }

  version = svn_repos_version();
  printf("lib version: %d.%d.%d\n", version->major, version->minor,
         version->patch);
  /* HOW to run editor over a particular sub path ??? */
  err = run_test("/home/dave/repo", fs_base, target, start, end, pool);
  if (err == SVN_NO_ERROR) {
    printf("no error in test\n");
  } else {
    fflush(stdout);
    fprintf(stdout, "HAD AN ERROR: \"%d\" \"%s\"\n",
            err->apr_err, err->message);
    svn_handle_error2(err, stderr, TRUE, "ed-test: ");
  }
  svn_pool_destroy(pool);
  return 0;
}

